package util;

import java.lang.management.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;

import ui.JImageResizerApp;

public class SystemMonitor extends Thread implements IEventCallback {
	private class Times {
		public long id;
		public long startCpuTime;
		public long startUserTime;
		public long endCpuTime;
		public long endUserTime;
		public long diffCpuTime;
		public long diffUserTime;
	}

	private final long interval;
	private final long threadId;
	private final long DEFAULT_MEMORY_UNIT = 1024;
	private HashMap<Long, Times> history;
	private long beginTime;
	private int processors;
	private volatile boolean isStopped;
	private ArrayList<Long> memoryUsed;
	private ArrayList<Double> cpuUsageList;
	private JImageResizerApp frame;

	public SystemMonitor(long checkInterval) {
		super("System monitor");
		isStopped = false;
		interval = checkInterval;
		threadId = getId();
		history = new HashMap<Long, Times>();
		memoryUsed = new ArrayList<Long>();
		cpuUsageList = new ArrayList<Double>();
		beginTime = System.nanoTime();
		processors = Runtime.getRuntime().availableProcessors();
		setDaemon(true);
		frame = null;
	}

	public void setCallback(JImageResizerApp f) {
		frame = f;
	}

	public void stopRequest() {
		isStopped = true;
	}

	public void finalize() {
		history.clear();
		history = null;
		memoryUsed.clear();
		memoryUsed = null;
	}

	/** Get CPU time in nanoseconds. */
	public long getCpuTime(long[] ids) {
		ThreadMXBean bean = ManagementFactory.getThreadMXBean();
		if (!bean.isThreadCpuTimeSupported())
			return 0L;

		long time = 0L;
		for (long i : ids) {
			long t = bean.getThreadCpuTime(ids[(int) i]);
			if (t != -1)
				time += t;
		}
		return time;
	}

	/** Get user time in nanoseconds. */
	public long getUserTime(long[] ids) {
		ThreadMXBean bean = ManagementFactory.getThreadMXBean();
		if (!bean.isThreadCpuTimeSupported())
			return 0L;
		long time = 0L;
		for (long i : ids) {
			long t = bean.getThreadUserTime(ids[(int) i]);
			if (t != -1)
				time += t;
		}
		return time;
	}

	/** Get system time in nanoseconds. */
	public long getSystemTime(long[] ids) {
		ThreadMXBean bean = ManagementFactory.getThreadMXBean();
		if (!bean.isThreadCpuTimeSupported())
			return 0L;
		long time = 0L;
		for (long i : ids) {
			long tc = bean.getThreadCpuTime(ids[(int) i]);
			long tu = bean.getThreadUserTime(ids[(int) i]);
			if (tc != -1 && tu != -1)
				time += (tc - tu);
		}
		return time;
	}

	/** Run the thread until interrupted. */
	public void run() {
		while (!isStopped) {
			updateCPUTime();
			updateMemoryByRuntime();
			try {
				// callback(IEventCallback.EVT_CPU_USAGE, (long)getCPUUsage());
				// callback(IEventCallback.EVT_MEMORY_USAGE,
				// (long)getHeapMemoryUsage());
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			beginTime = System.nanoTime();
			try {
				sleep(interval);
			}

			catch (InterruptedException e) {
				e.printStackTrace();
				break;
			}
		}
	}

	/** Update the memory record. */
	private void updateMemory() {
		final MemoryMXBean bean = ManagementFactory.getMemoryMXBean();
		memoryUsed.add(bean.getHeapMemoryUsage().getUsed());
	}

	private void updateMemoryByRuntime() {
		memoryUsed.add(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
	}

	/** Update the hash table of thread times. */
	private void updateCPUTime() {
		final ThreadMXBean bean = ManagementFactory.getThreadMXBean();

		final long[] ids = bean.getAllThreadIds();
		long totalDiffTime = 0L;
		for (long id : ids) {
			if (id == threadId)
				continue; // Exclude polling thread
			final long c = bean.getThreadCpuTime(id);
			final long u = bean.getThreadUserTime(id);
			if (c == -1 || u == -1)
				continue; // Thread died

			Times times = history.get(id);
			if (times == null) {
				times = new Times();
				times.id = id;
				times.startCpuTime = c;
				times.startUserTime = u;
				times.endCpuTime = c;
				times.endUserTime = u;
				times.diffCpuTime = 0;
				times.diffUserTime = 0;
				history.put(id, times);
			} else {
				times.diffCpuTime = c - times.endCpuTime;
				times.diffUserTime = u - times.endUserTime;
				times.endCpuTime = c;
				times.endUserTime = u;
			}
			totalDiffTime += times.diffCpuTime;
		}
		cpuUsageList.add(Double.valueOf(Math.min(99F, (double) totalDiffTime * 100F
				/ ((System.nanoTime() - beginTime) * (double) processors))));
	}

	/** Get total CPU time so far in nanoseconds. */
	public long getTotalCpuTime() {
		final Collection<Times> hist = history.values();
		long time = 0L;
		for (Times times : hist)
			time += times.endCpuTime - times.startCpuTime;
		return time;
	}

	/** Get total user time so far in nanoseconds. */
	public long getTotalUserTime() {
		final Collection<Times> hist = history.values();
		long time = 0L;
		for (Times times : hist)
			time += times.endUserTime - times.startUserTime;
		return time;
	}

	/** Get total system time so far in nanoseconds. */
	public long getTotalSystemTime() {
		return getTotalCpuTime() - getTotalUserTime();
	}

	/**
	 * Get difference between latest CPU time and current CPU time so far in
	 * nanoseconds.
	 */
	public long getTotalDiffCPUTime() {
		final Collection<Times> hist = history.values();
		long time = 0L;
		for (Times times : hist)
			time += times.diffCpuTime;
		return time;
	}

	/**
	 * Get difference between latest user time and current user time so far in
	 * nanoseconds.
	 */
	public long getTotalDiffUserTime() {
		final Collection<Times> hist = history.values();
		long time = 0L;
		for (Times times : hist)
			time += times.diffUserTime;
		return time;
	}

	/** Get current CPU Usage percentage. */
	public double getCPUUsage() {
		if (cpuUsageList.size() == 0)
			return 0d;
		return cpuUsageList.get(cpuUsageList.size() - 1).doubleValue();
	}

	/** Get current User Usage percentage. */
	public double getUserCPUUsage() {
		return Math.min(99F, (double) getTotalDiffUserTime() * 100F
				/ ((System.nanoTime() - beginTime) * (double) processors));
	}

	/** Get current Average Usage percentage. */
	public double getAvgCPUUsage() {
		if (cpuUsageList.size() == 0)
			return 0d;

		Collections.sort(cpuUsageList);
		double result = 0d;
		int avgIndex = -1;
		if (cpuUsageList.size() / 2 == 0) {
			avgIndex = (int) Math.floor(cpuUsageList.size() / 2);
			result = (cpuUsageList.get(avgIndex) + cpuUsageList.get(avgIndex + 1)) / 2;
		} else {
			avgIndex = (int) Math.ceil(cpuUsageList.size() / 2);
			result = cpuUsageList.get(avgIndex);
		}
		return result;
	}

	/** Get current memory used in kbytes) */
	public long getHeapMemoryUsage() {
		if (memoryUsed.size() == 0)
			return 0L;
		return memoryUsed.get(memoryUsed.size() - 1) / DEFAULT_MEMORY_UNIT;
	}

	/** Get maximum memory used in kbytes) */
	public long getMaxMemoryUsed() {
		return Collections.max(memoryUsed) / DEFAULT_MEMORY_UNIT;
	}

	/** Get average memory used in kbytes) */
	public long getAvgMemroyUsed() {
		if (memoryUsed.size() == 0)
			return 0L;
		long total = 0;
		for (Long m : memoryUsed)
			total += m.longValue();
		return total / (memoryUsed.size() * DEFAULT_MEMORY_UNIT);
	}

	public void callback(long... args) throws Exception {
		long evt = args[0];
		String msg = String.valueOf(args[1]);
		frame.callback(evt, msg);
	}
}
